#define LIBN1_PLUGMODE2

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <signal.h>
#include <unistd.h>

using namespace std;

#include "mpi.h"
#include "binfile.h"
#include "g6.h"

#define NEW_TASK 1
#define END_WORK 2
#define TASK_RESULT 3
#define GET_NEW_TASK 4

#define L_UPPER_BOUND 14

#define min(a,b) ((a)<(b)?(a):(b))

//функция предварительной оценки верхней границы сложности (сравниваются 3 сложности: f_{x_1}^0 f_{x_1}^1 | f_{x_1}^' f_{x_1}^1 | f_{x_1}^' f_{x_1}^0)
inline Int8 upb1(const Int32 f1, const Int32 f2) {
  return
    min(
      lib.get(f1) + lib.get(f2), 
      lib.get(f1 ^ f2) + min(
        lib.get(f1), 
        lib.get(f2)
      )
    );
}

class TLPNPClasses {
public:
  size_t y,xpos,ypos;
  Int64 yval;
  BinFile yfile;
  vector<Int32> xarray;
  bool havenext; 
  Int64 f;

  Int64 next() {
    Int64 ret = f;
    while(true) {
      xpos++;      
      if (xpos >= xarray.size()) {
        xpos = 0;
        if (!yfile.isEnd()) {
          yfile.read(yval);
          ypos += 4;
        } else {
          ypos = 0;
          y--;
          if (y == 4) {
            havenext = false;
            break;
          } else {
            ostringstream oss;
            oss << "binfile5vf" << y << "r.mbf";        
            yfile.open(oss.str(), BIN_FILE_READ);            
          }            
        }
      }
      if (upb1(yval, xarray[xpos]) >= L_UPPER_BOUND) {
        f = (yval << 32) ^ xarray[xpos];
        break;
      }
    }
    return ret;
  }
  
  TLPNPClasses(string filename) {
    ifstream config(filename.c_str());
    config >> y >> xpos >> ypos;
    this->havenext = true;
    ifstream xfile("5lp.txt");
    Int32 f;
    while(xfile >> f) xarray.push_back(f);
    xfile.close();
    
    ostringstream oss;
    oss << "binfile5vf" << y << "r.mbf"; 
    yfile.open(oss.str(), BIN_FILE_READ);  
    yfile.skip(ypos);  
  }
  
  void saveConfig(string filename) {
    ofstream config(filename.c_str());
    config << y << ' ' << xpos << ' ' << ypos << endl;
    config.close();    
  }
};

bool ending = false; // надо ли заканчивать работу

void ex_program(int sig) {
  ending = true;
}

int main(int argc, char** argv) {
  int rank, size;
	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD,&size);
	vector<Int64> funcs; // создается массив отправленных функций (чтобы не загружать сеть, а сервер помнил, что кому отправил)
	funcs.resize(size);	
	size_t working_nodes = size - 1; // количество работающих узлов
  MPI_Status state;	
  Int8 comp;  
  Int64 f;  
	cout << "Process with " << rank << " runned. PID is " << getpid() << '.' << endl; 
  //CTRL-C заканчивает работу
  (void) signal(SIGINT, ex_program);

	
	if (rank == 0) {
	  //master node
	  
	  cout << "To finish execution run: kill -n 2 " << getpid() << endl;
    	  
  	TLPNPClasses lpnp("upperbound6v.cfg");
	  if (lpnp.havenext) f = lpnp.next(); else ending = true;
	  BinFile comp14, comp15,compM;
	  comp14.open("binfile6vs14.mbf",BIN_FILE_APPEND);
	  comp15.open("binfile6vs15.mbf",BIN_FILE_APPEND);
	  compM.open("binfile6vsM.mbf",BIN_FILE_APPEND);	  	  
 
	  while(true) {
	    
	    MPI_Recv(&comp, 1, MPI_UNSIGNED_CHAR, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &state);
	    
	    if (state.MPI_TAG == GET_NEW_TASK) {
	      //отправка нового задания
	      if (!ending) {
	        //отправляем новое задание
    	    MPI_Send(&f, 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, NEW_TASK, MPI_COMM_WORLD);
    	    funcs[state.MPI_SOURCE] = f;
    	    f = lpnp.next();
    	    if (!ending) ending = !lpnp.havenext;
    	    
    	  } else {
    	    //заканчиваем работу
    	    MPI_Send(&f, 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, END_WORK, MPI_COMM_WORLD);
    	    working_nodes--;
    	    if (working_nodes == 0) break;
    	  }
  	  } else {
  	    if (state.MPI_TAG == TASK_RESULT) {
    	    //обработка полученного результата    	    
    	    if (comp == 14) comp14.write(funcs[state.MPI_SOURCE]);
    	    else if (comp == 15) comp15.write(funcs[state.MPI_SOURCE]);
    	    else compM.write(funcs[state.MPI_SOURCE]);
  	    }  	    
  	  }
	  }
	  lpnp.saveConfig("upperbound6v.cfg");
	} else {
	  //extend node
	  while(true) {
	    Int8 comp;
	    Int64 f;
	    MPI_Send(&comp, 1, MPI_UNSIGNED_CHAR, 0, GET_NEW_TASK, MPI_COMM_WORLD); //запросить новое задание
  	  MPI_Recv(&f, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &state); //получить новое задание	    
  	  if (state.MPI_TAG == NEW_TASK) {
    	  comp = g6evo(f);
  	    MPI_Send(&comp, 1, MPI_UNSIGNED_CHAR, 0, TASK_RESULT, MPI_COMM_WORLD); //отправить результат
	    } else if (state.MPI_TAG == END_WORK) {	      
	      // закончить выполнение заданий
	      break;
  	  }
	  }
	}
	MPI_Finalize();
	return 0;
}

